//=====================================================================
//
//  File:      ascmd.cs
//  Summary:   A command-line utility to execute Analysis Services XMLA scripts
//             or MDX queries
//  Authors:   Dave Wickert (dwickert@microsoft.com)
//  Date:	   03-Jan-2006
//
//---------------------------------------------------------------------
//
//  This file is part of the Microsoft SQL Server Code Samples.
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//This source code is intended only as a supplement to Microsoft
//Development Tools and/or on-line documentation.  See these other
//materials for detailed information regarding Microsoft code samples.
//
//THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF 
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//PARTICULAR PURPOSE.
//
//===================================================================== 

//
// 10.0.87.1
// Edits for going to SQL Server 2008
// Adding multi-user testing tool capabilities, commands, file parsing, etc.
// 
// Author: Dave Wickert (dwickert@microsoft.com)
// Date: 28-Mar-2008
//===================================================================== 

//
// Note: the code below contains additional lines to check for strong
// naming convention of obects, e.g. server names, usernames, passwords, etc.
// However, since we don't know what you guidelines will be we were not
// able to assume your specific values. Free from to change ParseArgs and add
// minimum, maximum and invalid object characters based on your 
// individual naming conventions.

// Line length (limit to 79 characters for formatting)
//       1         2         3         4         5         6         7
//34567890123456789012345678901234567890123456789012345678901234567890123456789

using System;
using System.Web;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Globalization;
using System.Threading;
using Microsoft.Samples.SqlServer.Properties; // the locSting resources

using Microsoft.AnalysisServices;             //AMO
using Microsoft.AnalysisServices.AdomdClient; //ADOMD.NET

// needed for Logon Win32 API call and user impersonation //
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Security.Permissions;

[assembly: CLSCompliant(true)]
namespace Microsoft.Samples.SqlServer.ASCmd
{
    internal static class NativeMethods
    {
        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassw_outord,
            int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
    }

    class CmdMain
	{

		#region Assembly Attribute Accessors

        //static string AssemblyTitle // not used yet
        //{
        //    get
        //    {
        //        // Get all Title attributes on this assembly
        //        object[] attributes = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
        //        // If there is at least one Title attribute
        //        if (attributes.Length > 0)
        //        {
        //            // Select the first one
        //            AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0];
        //            // If it is not an empty string, return it
        //            if (titleAttribute.Title != "")
        //                return titleAttribute.Title;
        //        }
        //        // If there was no Title attribute, or if the Title attribute was the empty string, return the .exe name
        //        return System.IO.Path.GetFileNameWithoutExtension(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
        //    }
        //}

		static string AssemblyVersion
		{
			get
			{
                return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
			}
		}

		static string AssemblyDescription
		{
			get
			{
				// Get all Description attributes on this assembly
				object[] attributes = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false);
				// If there aren't any Description attributes, return an empty string
				if (attributes.Length == 0)
					return "";
				// If there is a Description attribute, return its value
				return ((AssemblyDescriptionAttribute)attributes[0]).Description;
			}
		}

        static string AssemblyProduct // not used yet
        {
            get
            {
                // Get all Product attributes on this assembly
                object[] attributes = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), false);
                // If there aren't any Product attributes, return an empty string
                if (attributes.Length == 0)
                    return "";
                // If there is a Product attribute, return its value
                return ((AssemblyProductAttribute)attributes[0]).Product;
            }
        }

		static string AssemblyCopyright
		{
			get
			{
				// Get all Copyright attributes on this assembly
				object[] attributes = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false);
				// If there aren't any Copyright attributes, return an empty string
				if (attributes.Length == 0)
					return "";
				// If there is a Copyright attribute, return its value
				return ((AssemblyCopyrightAttribute)attributes[0]).Copyright;
			}
		}

        //static string AssemblyCompany // not used yet
        //{
        //    get
        //    {
        //        // Get all Company attributes on this assembly
        //        object[] attributes = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
        //        // If there aren't any Company attributes, return an empty string
        //        if (attributes.Length == 0)
        //            return "";
        //        // If there is a Company attribute, return its value
        //        return ((AssemblyCompanyAttribute)attributes[0]).Company;
        //    }
        //}

		static string AssemblyProcessorArchitecture
		{
			get
			{
                return System.Reflection.Assembly.GetExecutingAssembly().GetName().ProcessorArchitecture.ToString();
			}
		}

		#endregion
      
        #region Class Variables

        // message handling for loc strings
        static string Msg(string locString) { return (AssemblyProduct + ": " + locString); }

        // What options have been seen on the command line
        static bool Option_U_specified;
        static bool Option_P_specified;
        static bool Option_S_specified;
        static bool Option_d_specified;
        static bool Option_t_specified;
        static bool Option_tc_specified;
        static bool Option_i_specified;
        static bool Option_o_specified;
        static bool Option_T_specified;
        static bool Option_Tt_specifed;
        static bool Option_Tf_specified;
        static bool Option_Tl_specified;
        static bool Option_Td_specified;
        static bool Option_Q_specified;
        static bool Option_xc_specified;
        static bool Option_v_specified;
        
        // Variables for options
        static string UserName = "";
		static string Domain = "";
		static string Password = "";
        static string Server = "localhost";
        static string Instance = "";
        static string Database = "";
		static string InputFile = "";
		static string OutputFile = "";
        static string TraceFile = "";
        static string Query = "";
        static string ExtendedConnectstring = "";
        static string TraceFormat = "csv"; // use the -f option
        static string Timeout = "";
        static string ConnectTimeout = "";
        static string TraceTimeout = "5"; // trace is finished if no activity for 5 seconds
        static bool httpConnection;

        // provider to use -- should we have this an an option (i.e. -p) ??
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
        const string Provider = "MSOLAP.3"; // force AS2K5 provider

        // Variables for read/writing
		static StringBuilder sb = new StringBuilder(); // input stream
		static StreamWriter sw_out; //Output file
        static bool IsOutputFileOpen;
        static StreamWriter sw_trace; //Trace file
        static bool IsTraceFileOpen;
        static string TraceDelim = "|";
        static string TraceLevel = "high"; // default is to trace at the high level
        static long BeginEndBlockCount; // if 0 then OK to immediately finish trace
        static int TraceTimeoutCount; // if goes to 0, then finish trace regardless of BeginEndBlockCount
        static int TraceTimeoutCountReset; // the amount of time to wait once trace events stop
        static bool TraceStarted; // have the trace events started to flow?
        const int PollingInterval = 20; // Every 1/20th of a second (50ms) see if trace is done (BeginEndBlockCount drives to 0)
        static bool ExceptionSeen; // have we seen an exception in the xmla return stream

        static bool _DEBUGMODE; // Set to indicate that we need debug messages outputted

        // substitution table -- includes -v entries plus command-line arguments
        static Dictionary<string, string> substituteWith =
            new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase);

        // maximum timeout values (if used)
        const int maxTimeout = -1; // -1 means don't check

        // regular expressions used
        const string ScriptingVarRefRegex = @"\$\([a-zA-Z0-9_-]+\)";
        const string ScriptingVarNameRegex = @"^[a-zA-Z0-9_-]+=.*";
        const string XMLACommandRegex = 
                        @"(?sx)^(<Alter.*|
                                 <Backup.*|
                                 <Batch.*|
                                 <BeginTransaction.*|
                                 <Cancel.*|
                                 <ClearCache.*|
                                 <CommitTransaction.*|
                                 <Create.*|
                                 <Delete.*|
                                 <DesignAggregations.*|
                                 <Drop.*|
                                 <Insert.*|
                                 <Lock.*|
                                 <MergePartitions.*|
                                 <NotifyTableChange.*|
                                 <Process.*|
                                 <Restore.*|
                                 <RollbackTransaction.*|
                                 <SetPasswordEncryptionKey.*|
                                 <Statement.*|
                                 <Subscribe.*|
                                 <Synchronize.*|
                                 <Unlock.*|
                                 <Update.*|
                                 <UpdateCells.*)$";
        const string DiscoverRegex = @"(?sx)^<Discover.*$";
        const string ExecuteRegex = @"(?sx)^<Execute.*$";
        const string BatchRegex = @"^[\w\W]*?[\r\n]go\s";

        // formats
        const string SoapEnvFormat = 
            @"<Envelope xmlns='http://schemas.xmlsoap.org/soap/envelope/'>
                <Body>
                {0}
                </Body>
              </Envelope>";

		#endregion

        // --------------------------------------------------------------------
        // Main routine -- called with the command line arguments
        static int Main(string[] args)
		{
            try
            {   
                Console.WriteLine(AssemblyDescription);
                Console.WriteLine(Properties.Resources.locVersion, 
                            AssemblyVersion, AssemblyProcessorArchitecture);
                Console.WriteLine(AssemblyCopyright);

                // Parse the command line argument list, verify the options and
                // open any requested files
                try
                {
                    if (!ParseArgs(args))
                    {
                        return 1; // if it returns an error, just exit
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(Msg(Properties.Resources.locParseArgsErr), 
                                ex.Message);
                    return 1; // error raised
                }

                // Take the input stream and substitute any scripting variables within it
                SubstituteScriptingVariables(sb);

                // Impersonate the -U username (if needed) and then **execute** the
                // query or script. Note: http connections set the UID and PWD in
                // the connect string
                if (Option_U_specified && !httpConnection) 
                {
                    WindowsImpersonationContext impersonatedUser;
                    if (_DEBUGMODE) Console.WriteLine("Windows identity prior to impersonation : " + WindowsIdentity.GetCurrent().Name);
                    impersonatedUser = ImpersonateUser(UserName, Domain, Password);
                    if (_DEBUGMODE) Console.WriteLine("Windows identity after impersonation : " + WindowsIdentity.GetCurrent().Name);

                    ExecuteInput(ConnectString); // execute it
                    
                    impersonatedUser.Undo();
                    if (_DEBUGMODE) Console.WriteLine("Windows identity after impersonation undo : " + WindowsIdentity.GetCurrent().Name);
                }
                else
                {
                    ExecuteInput(ConnectString); // execute it
                }

                CloseFiles();
            }
            catch (Exception ex)
            {
                Console.WriteLine(Msg(Properties.Resources.locFailedErr), ex.Message);
                return 1; // error raised
            }
            if (Option_o_specified && ExceptionSeen)
                Console.WriteLine(Msg(Properties.Resources.locCheckOutputForErr));
            return (ExceptionSeen) ? 1 : 0;
		}

        // Regex match against input string??
        private static bool InputMatch(string regexString, StringBuilder sb)
        {
            string s = sb.ToString();
            bool b = Regex.IsMatch(s,regexString);
            return b;   
        }

        // Rest of the functions below are supporting routines.

        // --------------------------------------------------------------------
        // Supporting routine -- perform scripting variable substitution
        private static void SubstituteScriptingVariables(StringBuilder sb)
        {
            Regex expression = new Regex(ScriptingVarRefRegex);
            
            foreach ( Match m in expression.Matches(sb.ToString()))
            {
                // Pick out just the scripting variable name by remove leading and
                // trailing characters of the scripting variable
                string name = m.ToString().Replace("$(", "").Replace(")", "");

                // Perform the substitution of scripting variable to its value
                if (substituteWith.ContainsKey(name))
                {
                    // -v or system-reserved replacement
                    sb.Replace(m.ToString(), substituteWith[name]); 
                }
                else if ((Environment.GetEnvironmentVariable(name) != null) && 
                        (!name.StartsWith("ASCMD", StringComparison.CurrentCultureIgnoreCase)))
                {
                    // environment variable replacement (cannot be used for system-reserved)
                    sb.Replace(m.ToString(), Environment.GetEnvironmentVariable(name));
                }
                else
                {
                    // no match found, so eliminate the scripting variable by
                    // replacing it with a blank string
                    sb.Replace(m.ToString(), ""); 
                }
            }
            if (_DEBUGMODE) Console.WriteLine("After substitution, final input is: " + sb.ToString());
        }

        // --------------------------------------------------------------------
        // Supporting routine -- determine what the connect string should be
        private static string ConnectString
        {
            get
            {
                string cs = "";

                // Build the connect string as required
                // Note: some parameters might have legimite embedded doublequotes "
                //       if so, we marked them with // ** tag
                cs += "Provider=" + Provider;
                if (httpConnection)
                {
                    cs += ";Data Source=\"" + Server + "\"";
                }
                else
                {
                    cs += ";Data Source=\"" + Server;
                    if (Instance.Length > 0) cs += "\\" + Instance;
                    cs += "\"";
                }
                if (Database.Length > 0) cs += ";Database=\"" + Database.Replace("\"", "\"\"") + "\""; //**
                
                // Add http information, if requested
                if (httpConnection && (UserName.Length > 0))
                {
                    cs += ";UID=\"" + ((Domain.Length > 0) ? Domain + "\\" + UserName : UserName) + "\"";
                    cs += ";PWD=\"" + Password.Replace("\"", "\"\"") + "\""; // **
                }
                
                // Add timeout information, if requested
                if (Timeout.Length > 0) 
                        cs += ";Timeout=" + Timeout.ToString(CultureInfo.CurrentCulture);
                if (ConnectTimeout.Length > 0)
                        cs += ";Connect Timeout=" + ConnectTimeout.ToString(CultureInfo.CurrentCulture);

                // Add extended connectstring option
                if (ExtendedConnectstring.Length > 0) 
                        cs += ";" + ExtendedConnectstring.Replace("\"","\"\""); // **

                // Finally, add our application name :-)
                cs += ";SspropInitAppName=" + AssemblyProduct;

                if (_DEBUGMODE) Console.WriteLine("Connect string: {0}", cs);
                
                return cs;
            }
        }

        // --------------------------------------------------------------------
        // Supporting routine -- to logon the indicated Windows user (returns
        // the context to undo the impersonation after work is done)
        [SecurityPermission(SecurityAction.Assert, Flags =SecurityPermissionFlag.UnmanagedCode)]
        private static WindowsImpersonationContext ImpersonateUser(string logonUsername, string logonDomain, string logonPassword)
		{
            try
            {
                const int LOGON32_PROVIDER_DEFAULT = 0;
                //This parameter causes LogonUser to create a primary token.
                const int LOGON32_LOGON_INTERACTIVE = 2;

                IntPtr tokenHandle = new IntPtr(0);
                tokenHandle = IntPtr.Zero;

                // Call LogonUser to obtain a handle to an access token.
                if (_DEBUGMODE) Console.WriteLine("Calling LogonUser");
                bool returnValue = NativeMethods.LogonUser(logonUsername, logonDomain, logonPassword,
                    LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
                    ref tokenHandle);

                if (false == returnValue)
                {
                    int ret = Marshal.GetLastWin32Error();
                    Console.WriteLine(Msg(Properties.Resources.locLogonFailedErr), ret);
                    throw new System.ComponentModel.Win32Exception(ret);
                }

                if (_DEBUGMODE) Console.WriteLine("Did LogonUser Succeed? " + (returnValue ? "Yes" : "No"));
                if (_DEBUGMODE) Console.WriteLine("Value of Windows NT token: " + tokenHandle);

                // Check the identity.
                if (_DEBUGMODE) Console.WriteLine("Before impersonation: " + WindowsIdentity.GetCurrent().Name);

                // Impersonate using the token handle returned by LogonUser.
                using (WindowsIdentity newId = new WindowsIdentity(tokenHandle))
                {
                    return (newId.Impersonate());
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(Msg(Properties.Resources.locImpersonateErr), ex.Message);
                throw;
            }
		}

        // --------------------------------------------------------------------
        // Execute an input statement (MDX query or XMLA script)
        // *** This is the real work of the program -- the rest is supporting functions ***
        private static void ExecuteInput(string ConnectionString)
        {
            Server oServer = new Server();
            //Open up a connection
            try
            {
                oServer.Connect(ConnectionString);
            }
            catch (Exception ex)
            {
                Console.WriteLine(Msg(Properties.Resources.locAmoConnErr), ConnectionString, ex.Message);
                throw;
            }

            // Try to execute the query/script
            try
            {
                // Setup to trace events during execution (only if level is not "duration")
                if (Option_T_specified && !(TraceLevel == "duration") && !(TraceLevel == "duration-result"))
                {
                    // Initialize trace handler
                    oServer.SessionTrace.OnEvent += new TraceEventHandler(SessionTrace_OnEvent);

                    //Write some status stuff
                    if (_DEBUGMODE) Console.WriteLine("Trace connection status to " + Server + " := " + oServer.Connected.ToString(CultureInfo.CurrentCulture));
                    if (_DEBUGMODE) Console.WriteLine("Server := " + oServer.Name + " (Session:" + oServer.SessionID.ToString(CultureInfo.CurrentCulture) + ")");

                    // Start the trace
                    oServer.SessionTrace.Start();
                }

                // Execute the input batches
                ExecuteBatches(oServer);
          
                // Stop the trace (if running)            
                if (!oServer.SessionTrace.IsStarted) oServer.SessionTrace.Stop();

            }
            catch (Exception ex)
            {
                Console.WriteLine(Msg(Properties.Resources.locAdomdExecutionErr), ex.Message);
                oServer.Dispose();
                throw;
            }

            //Disconnect and end the session on the server
            try
            {
                oServer.Disconnect(true);
            }
            catch (Exception ex)
            {
                Console.WriteLine(Msg(Properties.Resources.locAmoDisconnErr), ex.Message);
                throw;
            }
            
            oServer.Dispose();
        }

        // --------------------------------------------------------------------
        // Execute the batches in the input stream (sb)
        //   A batch is a portion of the input stream separated by "GO" commands, i.e.
        //      <batch>
        //      GO
        //      <batch>
        //      GO
        // 
        private static void ExecuteBatches(Server oServer)
        {
            Regex expression = new Regex(BatchRegex,
                RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);

            // pickup the input stream, append a GO command and parse it
            MatchCollection matches = expression.Matches(sb.ToString() + "\ngo\n");

            if (matches.Count > 1) Output("<multiple-batches>");

            foreach (Match m in matches)
            {
                // Pick out just the scripting variable name by remove leading and
                // trailing characters of the scripting variable
                string fullText = m.Value;
                StringBuilder batch = new StringBuilder(fullText.Substring(0, fullText.Length - 3));

                if (_DEBUGMODE) Console.WriteLine("Batch input is: " + batch.ToString());
                ExecuteBatch(oServer, batch); // fire the batch
            } // loop for next batch

            if (matches.Count > 1) Output("</multiple-batches>");
        }

        // Now execute the batch itself (which is the lowest level of command to ascmd)
        private static void ExecuteBatch(Server oServer, StringBuilder inp)
        {
            // The result string for the execution
            string Result;

            // Wrap a stopwatch around execution
            Stopwatch sw = new Stopwatch();

            // If this is not an XMLA command (or Discover/Execute raw XMLA request) then
            // we assume that it is an un-encoded Statement without the XML tag.
            // This allows the end-user the convience of just entering a MDX or DMX statement
            // and we will wrap it for them.
            if (!InputMatch(XMLACommandRegex,inp) && !InputMatch(DiscoverRegex,inp) && !InputMatch(ExecuteRegex,inp))
            {
                string stmt = HttpUtility.HtmlEncode(inp.ToString());
                // Recreate in Input string -- wrapping with <Statement> tags
                inp = new StringBuilder(stmt.Length); // allocates space for new batch -- nothing in it
                inp.AppendFormat("<Statement>{0}</Statement>", stmt); // adds the batch input itself
            }

            // There are two different ways to execute the input: a raw XMLA request or an XMLA command.
            // If this is a raw request, then the batch starts with "Discover" or "Execute", else it is an XMLA command
            if (InputMatch(DiscoverRegex, inp) || InputMatch(ExecuteRegex, inp))
            {
                //--------------------------------------------------------------------------------
                // A raw XMLA request:
                // To run a custom full SOAP Envelope request on Analysis Services server, we
                // need to follow 5 steps:
                // 
                // 1. Start the XML/A request and specify its type (Discover or Execute).
                //    For native connections (direct over TCP/IP with DIME protocol), local 
                //    cube connections and stored procedures connections we don't need to
                //    specify the XML/A request type in advance (an Undefined value is
                //    available). But for HTTP and HTTPS connections we need to.
                //
                // 2. Write the xml request (as an xml soap envelope containing the command
                //    for Analysis Services).
                //
                // 3. End the xml request; this will send the previously written request to the 
                //    server for execution.
                //
                // 4. Read/Parse the xml response from the server (with System.Xml.XmlReader).
                //
                // 5. Close the System.Xml.XmlReader from the previous step, to release the 
                //    connection to be used after.
                //--------------------------------------------------------------------------------

                XmlaRequestType r = XmlaRequestType.Undefined;
                if (InputMatch(DiscoverRegex, inp)) r = XmlaRequestType.Discover;
                if (InputMatch(ExecuteRegex, inp)) r = XmlaRequestType.Execute;

                // Wrap the request with a soap envelope and body
                string s = inp.ToString();
                s = String.Format(CultureInfo.CurrentCulture, SoapEnvFormat, s);

                // Start the stopwatch
                sw.Start();

                // Execute the query/script and gather the results
                XmlWriter _xw = oServer.StartXmlaRequest(r);
                _xw.WriteRaw(s);
                using (XmlReader _xr = oServer.EndXmlaRequest())
                {
                    // Stop the stopwatch
                    sw.Stop();

                    // Move in the reader to where the real content begins
                    _xr.MoveToContent();

                    // Skip past the soap envelope and body (if they exist)
                    if (!_xr.EOF && (_xr.Name == "soap:Envelope")) _xr.Read();
                    if (!_xr.EOF && (_xr.Name == "soap:Body")) _xr.Read();

                    // Gather the results and output them
                    Result = GatherResults(_xr);

                    // Close the System.Xml.XmlReader, to release the connection for
                    // future use.
                    _xr.Close();
                }
            }
            else
            {
                //--------------------------------------------------------------------------------
                // An XMLA Command request:

                sw.Start(); // start the stopwatch
                XmlaResultCollection _xrc = oServer.Execute(inp.ToString());
                sw.Stop();

                // Gather the results and output them
                Result = GatherResults(_xrc);
            }
            // Output the result
            Output(Result);

            // If requested, wait for tracing to finish
            if (Option_T_specified)
            {
                if ((TraceLevel == "duration") || (TraceLevel == "duration-result"))
                {
                    DurationTrace(sw, Result);
                }
                else
                {
                    // is high, medium, low
                    WaitForTraceToFinish();
                }
            }
        }
        
        // --------------------------------------------------------------------
        // Supporting routines -- gather the results

        private static string GatherResults(XmlReader Results)
        {
            string s = Results.ReadOuterXml();

            // -----------------------------------
            // Look for errors.
            // -----------------------------------
            // As an initial pass, we'll just look for some special tags in the
            // stream. This works because the underlying data in the xml stream
            // is html encoded -- thus the only tags that should exist is if there
            // are errors
            if (s.Contains("</soap:Fault>")||   // xsd-detected errors
                s.Contains("</Exception>")      // server-detected errors
                )
            {
                ExceptionSeen = true; // tell the main program that we saw errors
            }

            return s;
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
        private static string GatherResults(XmlaResultCollection Results)
        {
            // XML namespace constants used
            const string xmlns_xmla = "xmlns=\"urn:schemas-microsoft-com:xml-analysis\"";
            const string xmlns_multipleresults = "xmlns=\"http://schemas.microsoft.com/analysisservices/2003/xmla-multipleresults\"";
            const string xmlns_mddataset = "xmlns=\"urn:schemas-microsoft-com:xml-analysis:mddataset\"";
            const string xmlns_xsi = "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"";
            const string xmlns_xsd = "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"";
            const string xmlns_empty = "xmlns=\"urn:schemas-microsoft-com:xml-analysis:empty\"";
            const string xmlns_exception = "xmlns=\"urn:schemas-microsoft-com:xml-analysis:exception\"";

            // Format strings constants used
            const string fs_Error = "<Error ErrorCode=\"{0}\" Description=\"{1}\" Source=\"{2}\" HelpFile=\"{3}\" />";
            const string fs_Warning = "<Warning WarningCode=\"{0}\" Description=\"{1}\" Source=\"{2}\" HelpFile=\"{3}\" />";

            // Start to build the output
            StringBuilder _output = new StringBuilder(
                String.Format(CultureInfo.CurrentCulture,"<return {0}>", xmlns_xmla));

            // If there are multiple resultsets, then add the grouping element 
            if (Results.Count > 1)
                _output.Append(String.Format(CultureInfo.CurrentCulture,
                    "<results {0}>", xmlns_multipleresults));
 
            // loop through each result in the result set
            foreach (XmlaResult _xr in Results)
            {
                // Is there a value for this result?
                if (_xr.Value.Length > 0)
                {   // Yes, indicate its type 
                    _output.Append(String.Format(CultureInfo.CurrentCulture,
                        "<root {0} {1} {2}>", xmlns_mddataset, xmlns_xsi, xmlns_xsd));
                    _output.Append(_xr.Value); // include the value in the stream
                }
                else
                {   // Nope, output the empty set for the root
                    _output.Append(String.Format(CultureInfo.CurrentCulture,
                        "<root {0}>", xmlns_empty));
                }
                
                // Do we have some messages associated with the result? If so, output them
                if (_xr.Messages.Count > 0)
                {
                    if (ErrorsExist(_xr))
                        _output.Append(String.Format(CultureInfo.CurrentCulture,
                            "<Exception {0} />", xmlns_exception));
                    
                    // Output the messages
                    _output.Append(String.Format(CultureInfo.CurrentCulture,
                        "<Messages {0}>", xmlns_exception));
                    foreach (XmlaMessage _xm in _xr.Messages)
                    {
                        if (_xm is XmlaError) // determine type type
                        {
                            ExceptionSeen = true;    // ERROR bubbles up for the "main" return value

                            int ErrorCode = ((XmlaError)_xm).ErrorCode;
                            _output.Append(String.Format(CultureInfo.CurrentCulture, fs_Error, 
                                ((uint)ErrorCode).ToString(CultureInfo.CurrentCulture),
                                HttpUtility.HtmlEncode(_xm.Description), _xm.Source, _xm.HelpFile));
                            
                        }
                        else
                        {
                            int WarningCode = ((XmlaWarning)_xm).WarningCode;
                            _output.Append(String.Format(CultureInfo.CurrentCulture, fs_Warning, 
                                ((uint)WarningCode).ToString(CultureInfo.CurrentCulture),
                                HttpUtility.HtmlEncode(_xm.Description), _xm.Source, _xm.HelpFile));
                        }
                    }
                    _output.Append("</Messages>");
                }
                _output.Append("</root>");
            }
            if (Results.Count > 1) _output.Append("</results>");
            
            _output.Append("</return>");

            // Return the string we've constructed
            return _output.ToString();
        }

        private static bool ErrorsExist(XmlaResult _xr)
        {
            bool ret = false;
            foreach (XmlaMessage _xm in _xr.Messages)
            {
                if (_xm is XmlaError) ret = true;
            }
            return ret;
        }

        private static void WaitForTraceToFinish()
        {
            int delay = Int32.Parse(TraceTimeout, CultureInfo.CurrentCulture);
            TraceTimeoutCount = TraceTimeoutCountReset = delay * PollingInterval; // TraceTimeoutCount is in 1/4 seconds
            // Wait for trace to start to flow
            while (!TraceStarted) { Thread.Sleep(1); } // loop every 20ms waiting for trace to start
            // Wait BeginEndBlockCount becomes 0 or TraceTimeoutCount expires
            while (TraceTimeoutCount > 0)
            { // loop forever until TraceFinished
                if (BeginEndBlockCount == 0) return;
                Thread.Sleep( 1000 / PollingInterval); // wait a polling interval
                TraceTimeoutCount--;
            }
            // TraceTimeoutCount expired -- just exit
        }

        // -------------------------------------------------------------------
        // Supporting routines which handle the trace events
        // Remove control characters, i.e. LF, CR, TAB, etc. and replace them with spaces
        private static string RemoveControlChars(string s)
        {
            StringBuilder sb = new StringBuilder(s);
            for (int i = 0; i < s.Length; i++)
            {
                if (Char.IsControl(sb[i]))
                    sb[i] = ' '; // replace all control characters with a space
            }
            return sb.ToString();
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        static void SessionTrace_OnEvent(object sender, TraceEventArgs e)
        {
            string EventClass = e.EventClass.ToString();
            //if (_DEBUGMODE) Console.WriteLine("SessionTrace_OnEvent fired");

            TraceStarted = true; // indicated that we have started to see trace events

            // keep the begin-end block count
            if (EventClass.Contains("Begin")) BeginEndBlockCount++;
            if (EventClass.Contains("End") && (BeginEndBlockCount > 0)) BeginEndBlockCount--;

            // based on the trace level, decide if we should record the event
            // high just falls through -- everything is recorded
            if (TraceLevel == "medium" || TraceLevel == "low")
            {
                if ((EventClass == "ProgressReportCurrent") || 
                    //(EventClass == "ProgressReportBegin") || 
                    //(EventClass == "ProgressReportEnd") || 
                    (EventClass == "Notification")) return; // ignore this event
            }
            if (TraceLevel == "low")
            {
                if (!(EventClass.Contains("End") ||
                    EventClass.Contains("Error"))) return; // if EventClass doesn't contain 'End' or 'Error', ignore this event
            }

            switch (TraceFormat)
            {
                case "text":  // a text interpretation of the event
                    {
                        StringBuilder tm = new StringBuilder(); // trace message
                        tm.Append(e.CurrentTime.ToString(CultureInfo.CurrentCulture));
                        tm.Append(", " + e.EventClass.ToString());
                        tm.Append("." + e.EventSubclass.ToString());
                        foreach (TraceColumn tc in Enum.GetValues(typeof(TraceColumn)))
                        {
                            if ((tc.ToString() != "CurrentTime") &&
                                (tc.ToString() != "EventClass") &&
                                (tc.ToString() != "EventSubclass"))
                            {
                                string val = e[tc];
                                if (null != val)
                                {
                                    if (tm.Length != 0) tm.Append(", ");
                                    string v = tc + ": " + RemoveControlChars(val);
                                    tm.Append(v);
                                }
                            }
                        }
                        // Note: For text, output nothing for 'Result' since it is alwasy blank for trace events

                        Trace(tm.ToString()); // write trace line
                        break;
                    }
                case "csv":	// a csv interpreation of the event
                    {
                        StringBuilder tm = new StringBuilder();
                        tm.Append(e.CurrentTime.ToString(CultureInfo.CurrentCulture));
                        tm.Append(TraceDelim + e.EventClass.ToString());
                        tm.Append(TraceDelim + e.EventSubclass.ToString());
                        foreach (TraceColumn tc in Enum.GetValues(typeof(TraceColumn)))
                        {
                            if ((tc.ToString() != "CurrentTime") &&
                                (tc.ToString() != "EventClass") &&
                                (tc.ToString() != "EventSubclass"))
                            {
                                string val = e[tc];
                                if (tm.Length != 0) tm.Append(TraceDelim);
                                if (null != val) tm.Append(RemoveControlChars(val));
                            }
                        }
                        tm.Append(TraceDelim); // For csv, 'Result' is always blank for trace events

                        Trace(tm.ToString()); // write trace line
                        break;
                    }
            }
            TraceTimeoutCount = TraceTimeoutCountReset; // reset the no activity timer
        }

        private static void DurationTrace(Stopwatch sw, string Result)
        {
            string now = DateTime.Now.ToString(CultureInfo.CurrentCulture);
            string Duration = sw.ElapsedMilliseconds.ToString(CultureInfo.CurrentCulture);
            switch (TraceFormat)
            {
                case "text":
                    {
                        StringBuilder tm = new StringBuilder(); // trace message
                        tm.Append(now);
                        tm.Append(", Duration: " + Duration);
                        tm.Append(", DatabaseName: " + Database);
                        tm.Append(", TextData: " + RemoveControlChars(sb.ToString()));
                        tm.Append(", ServerName: " + Server);
                        if (TraceLevel == "duration-result") tm.Append(", Result: " + Result);
                        Trace(tm.ToString()); // write trace line
                        break;
                    }
                case "csv":	// a csv interpreation of the event
                    {
                        StringBuilder tm = new StringBuilder();
                        tm.Append(now);
                        tm.Append(TraceDelim); // EventClass
                        tm.Append(TraceDelim); // EventSubclass
                        foreach (TraceColumn tc in Enum.GetValues(typeof(TraceColumn)))
                        {
                            if ((tc.ToString() != "CurrentTime") &&
                                (tc.ToString() != "EventClass") &&
                                (tc.ToString() != "EventSubclass"))
                            {
                                switch (tc.ToString())
                                {
                                    case "Duration":
                                        {
                                            tm.Append(TraceDelim);
                                            tm.Append(Duration);
                                            break;
                                        }
                                    case "DatabaseName":
                                        {
                                            tm.Append(TraceDelim);
                                            tm.Append(Database);
                                            break;
                                        }
                                    case "TextData":
                                        {
                                            tm.Append(TraceDelim);
                                            string val = sb.ToString();
                                            tm.Append(RemoveControlChars(val));
                                            break;
                                        }
                                    case "ServerName":
                                        {
                                            tm.Append(TraceDelim);
                                            tm.Append(Server);
                                            break;
                                        }
                                    default:
                                        {
                                            tm.Append(TraceDelim);
                                            break;
                                        }
                                }
                            }
                        }
                        tm.Append(TraceDelim);
                        if (TraceLevel == "duration-result") tm.Append(Result);

                        Trace(tm.ToString()); // write trace line
                        break;
                    }
            }
        }

        // --------------------------------------------------------------------
        // Supporting routines -- to generate lines in the "Output and Trace files
        private static void Output(string msg)
        {
            if (Option_o_specified)
            {
                // Add support for NUL device
                if (!(OutputFile.Equals("NUL", StringComparison.CurrentCultureIgnoreCase)) &&
                    !(OutputFile.StartsWith("NUL:", StringComparison.CurrentCultureIgnoreCase)))
                {
                    // file open delayed until absolutely needed (otherwise we end up overwriting it
                    // unnecessarily
                    if (!IsOutputFileOpen)
                    {
                        // open the file
                        try
                        {
                            sw_out = new StreamWriter(OutputFile, false, System.Text.Encoding.UTF8); // does an overwrite
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(Msg(Properties.Resources.locOutputFileOpenErr), ex.Message);
                            throw;
                        }
                        IsOutputFileOpen = true;
                    }
                    sw_out.WriteLine(msg);
                }
            }
            else
            {
                Console.WriteLine(msg);
            }
        }

        private static void Trace(string msg)
        {

            if (!IsTraceFileOpen)
            {
                bool appendFlag;

                // file open delayed until absolutely needed
                try
                {
                    // if doing duration trace and file already exists, then don't overwrite
                    // but instead do an append
                    appendFlag = (((TraceLevel == "duration") || (TraceLevel == "duration-result")) &&
                                        File.Exists(TraceFile)) ? true : false;
                    sw_trace = new StreamWriter(TraceFile, appendFlag, System.Text.Encoding.UTF8);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(Msg(Properties.Resources.locTraceFileOpenErr), ex.Message);
                    throw;
                }
                IsTraceFileOpen = true;

                // Ok Trace file is now ready to go

                // If csv trace file format and we are not appending to the end, then
                // output the header line in the trace
                if ((TraceFormat == "csv") && !appendFlag)
                {
                    StringBuilder tm = new StringBuilder();
                    tm.Append("CurrentTime" + TraceDelim + 
                        "EventClass" + TraceDelim + "EventSubclass");
                    foreach (TraceColumn tc in Enum.GetValues(typeof(TraceColumn)))
                    {
                        if ((tc.ToString() != "CurrentTime") &&
                            (tc.ToString() != "EventClass") &&
                            (tc.ToString() != "EventSubclass"))
                        {
                            tm.Append(TraceDelim);
                            tm.Append(tc);
                        }
                    }
                    tm.Append(TraceDelim + "Result");

                    Trace(tm.ToString());
                }
            }

            // write out the trace line
            try
            {
                sw_trace.WriteLine(msg);
            }
            catch (Exception ex)
            {
                Console.WriteLine(Msg(Properties.Resources.locTraceWriteErr), ex.Message);
                throw;
            }
        }

        // --------------------------------------------------------------------
        // Supporting routines -- close all files
        private static void CloseFiles()
        {
            // OK, we are all done. Perform some file cleanup and exit.
            if (IsOutputFileOpen) CloseFile(sw_out, Msg(Properties.Resources.locOutputFileCloseErr));
            if (IsTraceFileOpen) CloseFile(sw_trace, Msg(Properties.Resources.locTraceFileCloseErr));
        }
        
        private static void CloseFile(StreamWriter sw, string errorMsg)
        {
            try
            {
                sw.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine(errorMsg,ex.Message);
                throw;
            }
        }

        // --------------------------------------------------------------------
        // Supporting routine -- to parse the command line
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        private static bool ParseArgs(string[] args)
		{
            if (args.Length == 0) // nothing on the command-line
            { 
                // as a minimum -i or -Q must be specified
                Console.WriteLine(Msg(Properties.Resources.locInpEitherErr)); 
                ShowHelp(); return false; 
            }

            GetScriptingVariables(); // Get scripting variables from the OS

			// Now loop through the command-line args
            for (int i = 0; i < args.Length; i++)
			{
                string arg = args[i].ToString(CultureInfo.CurrentCulture);
				if (_DEBUGMODE) Console.WriteLine(arg);
	
				switch (arg)
				{
                    case "/?":  // Unix way of requesting help
                    case "-?":	// and this is the Windows way :-)
                        {
                            ShowHelp();
							return false;
						}
                    case "-U":	// User Id
						{
                            if (Option_U_specified) return OptionSeen("-U");
                            Option_U_specified = true;

                            if (i == (args.Length - 1)) return MissingArg("-U"); // option must have an argument
                            UserName = args[++i].ToString(CultureInfo.CurrentCulture);
                            if (_DEBUGMODE) Console.WriteLine("*" + UserName);
                            break;
						}
					case "-P":	// Password
						{
                            if (Option_P_specified) return OptionSeen("-P");
                            Option_P_specified = true;

                            if (i == (args.Length - 1)) return MissingArg("-P"); // option must have an argument
                            Password = args[++i].ToString(CultureInfo.CurrentCulture);
                            if (_DEBUGMODE) Console.WriteLine("*" + Password);
                            break;
						}
					case "-S":	// Server
						{
                            if (Option_S_specified) return OptionSeen("-S");
                            Option_S_specified = true;

                            if (i == (args.Length - 1)) return MissingArg("-S"); // option must have an argument
                            Server = args[++i].ToString(CultureInfo.CurrentCulture); 
                            if (_DEBUGMODE) Console.WriteLine("*" + Server);
                            break;
						}
					case "-d":	// Database
                        {
                            if (Option_d_specified) return OptionSeen("-d");
                            Option_d_specified = true;

                            if (i == (args.Length - 1)) return MissingArg("-d"); // option must have an argument
                            Database = args[++i].ToString(CultureInfo.CurrentCulture);
                            if (_DEBUGMODE) Console.WriteLine("*" + Database);
                            break;
						}
                    case "-t":	// Timeout (for query)
                        {
                            if (Option_t_specified) return OptionSeen("-t");
                            Option_t_specified = true;

                            if (i == (args.Length - 1)) return MissingArg("-t"); // option must have an argument
                            Timeout = args[++i].ToString(CultureInfo.CurrentCulture);
                            if (_DEBUGMODE) Console.WriteLine("*" + Timeout.ToString(CultureInfo.CurrentCulture));
                            break;
                        }
                    case "-tc":	// Timeout (for connections)
                        {
                            if (Option_tc_specified) return OptionSeen("-tc");
                            Option_tc_specified = true;

                            if (i == (args.Length - 1)) return MissingArg("-tc"); // option must have an argument
                            ConnectTimeout = args[++i].ToString(CultureInfo.CurrentCulture);
                            if (_DEBUGMODE) Console.WriteLine("*" + ConnectTimeout.ToString(CultureInfo.CurrentCulture));
                            break;
                        }
                    case "-i":	// InputFile
						{
                            if (Option_i_specified) return OptionSeen("-i");
                            Option_i_specified = true;

                            if (i == (args.Length - 1)) return MissingArg("-i"); // option must have an argument
                            InputFile = args[++i].ToString(CultureInfo.CurrentCulture);
                            if (_DEBUGMODE) Console.WriteLine("*" + InputFile);
                            // Validate the file exists
                            if (File.Exists(InputFile))
                            {
                                // Read in the query or script from the input file
                                try
                                {
                                    // Force the reader to be UTF8 and to look for byte-order marker in file
                                    StreamReader sr = new StreamReader(InputFile,System.Text.Encoding.UTF8,true);
                                    sb.Append(sr.ReadToEnd().Trim());
                                }
                                catch (Exception ex)
                                {
                                    Console.WriteLine(Msg(Properties.Resources.locInpFileGeneralErr), InputFile, ex.Message);
                                    throw;
                                }
                                // If input file is empty, return an error.
                                if (sb.Length == 0)
                                {
                                    Console.WriteLine(Msg(Properties.Resources.locInpFileEmptyErr), InputFile);
                                    return false;
                                }
                            }
                            else
                            {
                                // Input file does not exist
                                Console.WriteLine(Msg(Properties.Resources.locInpFileNotExistErr), InputFile);
                                return false;
                            }
							break;
						}
					case "-o":	// OutputFile
						{
                            if (Option_o_specified) return OptionSeen("-o");
                            Option_o_specified = true;

                            if (i == (args.Length - 1)) return MissingArg("-o"); // option must have an argument
                            OutputFile = args[++i].ToString(CultureInfo.CurrentCulture);
                            if (_DEBUGMODE) Console.WriteLine("*" + OutputFile);
                            // Make sure the output file name format is OK
                            if (!CheckValidChars(Properties.Resources.locNameOutputFile, 
                                        1, -1, "", OutputFile)) return false;
                            break;
						}
                    case "-Q":	// command line query
                        {
                            if (Option_Q_specified) return OptionSeen("-Q");
                            Option_Q_specified = true;

                            if (i == (args.Length - 1)) return MissingArg("-Q"); // option must have an argument
                            Query = args[++i].ToString(CultureInfo.CurrentCulture);
                            if (_DEBUGMODE) Console.WriteLine("*" + "\"" + Query + "\"");

                            // If input file is empty, return an error.
                            if (Query.Length == 0)
                            {
                                Console.WriteLine(Msg(Properties.Resources.locOptionQEmptyErr));
                                return false;
                            }
                            else
                            {
                                sb.Append(Query.Trim());
                            }
                            break;
                        }
                    case "-T":	// TraceFile
						{
                            if (Option_T_specified) return OptionSeen("-T");
                            Option_T_specified = true;

                            if (i == (args.Length - 1)) return MissingArg("-T"); // option must have an argument
                            TraceFile = args[++i].ToString(CultureInfo.CurrentCulture);
                            if (_DEBUGMODE) Console.WriteLine("*" + TraceFile);
                            // Make sure the trace file name format is OK
                            if (!CheckValidChars(Properties.Resources.locNameTraceFile, 
                                        1, -1, "", TraceFile)) return false;
							break;
						}
                    case "-Tf":	// Trace file format (only valid if -T is also specified)
                        {
                            if (Option_Tf_specified) return OptionSeen("-Tf");
                            Option_Tf_specified = true;

                            if (i == (args.Length - 1)) return MissingArg("-Tf"); // option must have an argument
                            TraceFormat = args[++i].ToString(CultureInfo.CurrentCulture);
                            if (_DEBUGMODE) Console.WriteLine("*" + TraceFormat.ToString(CultureInfo.CurrentCulture));
                            break;
                        }
                    case "-Tl":	// Trace file level (only valid if -T is also specified)
                        {
                            if (Option_Tl_specified) return OptionSeen("-Tl");
                            Option_Tl_specified = true;

                            if (i == (args.Length - 1)) return MissingArg("-Tl"); // option must have an argument
                            TraceLevel = args[++i].ToString(CultureInfo.CurrentCulture);
                            if (_DEBUGMODE) Console.WriteLine("*" + TraceLevel.ToString(CultureInfo.CurrentCulture));
                            break;
                        }
                    case "-Td":	// Trace file delimiter (default is "|", vertical bar or pipe)
                        {
                            if (Option_Td_specified) return OptionSeen("-Td");
                            Option_Td_specified = true;

                            if (i == (args.Length - 1)) return MissingArg("-Td"); // option must have an argument
                            TraceDelim = args[++i].ToString(CultureInfo.CurrentCulture);
                            if (_DEBUGMODE) Console.WriteLine("*" + ExtendedConnectstring.ToString(CultureInfo.CurrentCulture));
                            break;
                        }
                    case "-Tt":	// Timeout (for trace) -- indicates the amount of inactivity that signals trace is complete
                        {
                            if (Option_Tt_specifed) return OptionSeen("-Tt");
                            Option_Tt_specifed = true;

                            if (i == (args.Length - 1)) return MissingArg("-Tt"); // option must have an argument
                            TraceTimeout = args[++i].ToString(CultureInfo.CurrentCulture);
                            if (_DEBUGMODE) Console.WriteLine("*" + TraceTimeout.ToString(CultureInfo.CurrentCulture));
                            break;
                        }
                    case "-xc":	// Extended connectstring
                        {
                            if (Option_xc_specified) return OptionSeen("-xc");
                            Option_xc_specified = true;

                            if (i == (args.Length - 1)) return MissingArg("-xc"); // option must have an argument
                            ExtendedConnectstring = args[++i].ToString(CultureInfo.CurrentCulture);
                            if (_DEBUGMODE) Console.WriteLine("*" + ExtendedConnectstring.ToString(CultureInfo.CurrentCulture));
                            // No checks made -- whatever you enter will be placed on the connect sttring
                            break;
                        }
					case "-v":	// Set scripting variables
						{
                            if (Option_v_specified) return OptionSeen("-v");
                            Option_v_specified = true;

                            // -v options are a bit different.
                            // They are in the form '-v name=value name=value name=value'

                            // Loop through arguments until we no longer get a name=value pair 
                            while (i < (args.Length - 1)) // don't loop off the end
                            {
                                string Entry = args[++i].ToString(CultureInfo.CurrentCulture);

                                // look to see if the arg is name=value where name is a
                                // valid scripting variable
                                Regex expression = new Regex(ScriptingVarNameRegex);
                                if (!expression.IsMatch(Entry))
                                {
                                    i -= 1; // Nope, backup and retry the argument as an option 
                                    break;
                                }
                                else
                                {
                                    // Ok if we got here then this is a -v entry
                                    // Parse it into its name=value pair
                                    string[] ParsedEntry = Entry.Split(new Char[] {'='}, 2);
                                    string name = ParsedEntry[0];
                                    string value = ParsedEntry[1];
                                    if (_DEBUGMODE) Console.WriteLine("-v Name:*" + name);
                                    if (_DEBUGMODE) Console.WriteLine("-v Value:*" + value);
                                    // Validate the name -- it must not start with 'ASCMD'
                                    // that is reservered for command-line options
                                    if (name.StartsWith("ASCMD", 
                                                StringComparison.CurrentCultureIgnoreCase))
                                    {
                                        Console.WriteLine(Msg(Properties.Resources.locScriptingVarInvalidNameErr), Entry);
                                        return false;
                                    }
                                    else
                                    {
                                        // add it to substitutionWith table
                                        if (!substituteWith.ContainsKey(name))
                                        {
                                            // Entry is not already known, add it
                                            substituteWith.Add(name, value);
                                        }
                                        else
                                        {
                                            // Entry already there, override it
                                            // Note: this means that if a name is specified
                                            // more than once, then the last name=value pair
                                            // on the command line wins
                                            substituteWith[name] = value;
                                        }
                                    }
                                }
                            }
                            break;
						}
                    case "-DEBUG":	// set debug mode
                        {
                            // no argument
                            _DEBUGMODE = true;
                            break;
                        }
                    default: // error
                        {
                            Console.WriteLine(Msg(Properties.Resources.locUnknownOptionErr),arg);
                            return false;
                        }
                }
			}

            // Verify the current settings -- returns false if an error found
            if (!ValidateSettings()) return false;
            
            // Verify input stream
            // 1) command-line must have either -i or -Q, but not both
            // 2) there must be something to actually execute (some script or
            //    query)
            if (Option_i_specified && Option_Q_specified)
            { // both
                Console.WriteLine(Msg(Properties.Resources.locInpNotBothErr));
                return false;
            }
            else if (!(Option_i_specified || Option_Q_specified))
            { // either -i or -Q must be present
                Console.WriteLine(Msg(Properties.Resources.locInpEitherErr));
                return false;
            }
            else if (sb.Length == 0)
            {
                // If we got here then we don't have anything to execute,
                // Note: This is not really needed, because empty checking is 
                // also in both the -Q and -i option parsing
                Console.WriteLine(Msg(Properties.Resources.locNoInpErr)); //
                return false;
            }

            // You cannot run trace over an http or https connection
            if (Option_T_specified && httpConnection)
            {
                Console.WriteLine(Msg(Properties.Resources.locTraceNoHttpErr));
                return false;
            }

            // Set scripting variables
            SetScriptingVariables();

            return true; // All of the options are parsed and files are open ready to go
		}
        // --------------------------------------------------------------------
        // Helper routines for ParseArgs

        private static bool ValidateSettings()
        {
            if (!ValidateOption_S) return false; // This must be done first so the httpConnection can be set first
            if (!ValidateOption_U) return false;
            if (!ValidateOption_P) return false;
            if (!ValidateOption_d) return false;
            if (!ValidateTimeout("-t", Timeout)) return false;
            if (!ValidateTimeout("-tc", ConnectTimeout)) return false;
            if (!ValidateOption_Tf) return false;
            if (!ValidateOption_Tl) return false;
            if (!ValidateOption_Td) return false;
            if (!ValidateTimeout("-Tt", TraceTimeout)) return false;
            return true;
        }

        // -S validate
        // NOTE: This must be done first so the httpConnection can be set first
        private static bool ValidateOption_S
        {
            get
            {
                // Is this a http or https connection??
                string s = Server.ToLower(CultureInfo.CurrentCulture);
                if (s.StartsWith("http://") || s.StartsWith("https://"))
                {
                    httpConnection = true;
                    if (_DEBUGMODE) Console.WriteLine("Http Server:*" + Server);
                    if (!CheckValidChars(Properties.Resources.locNameServer,
                                8, -1, "", Server)) return false;
                }
                else
                {
                    // normal client-server connections -- either just the server or multi-instance
                    httpConnection = false;
                    if (Server.Contains("\\")) // backslash anywhere on line?
                    {
                        // multi-instance server name (i.e. <server>\<instance>
                        string[] ParsedServer = Server.Split(new Char[] {'\\'}, 2);
                        string partServer = ParsedServer[0];
                        string partInstance = ParsedServer[1];
                        if (_DEBUGMODE) Console.WriteLine("Server:*" + partServer);
                        if (_DEBUGMODE) Console.WriteLine("Instance:*" + partInstance);
                        // Check for valid server and instance name
                        // Note: the \ character not in Invalid char list since we support named instances
                        if (!CheckValidChars(Properties.Resources.locNameServer,
                                    1, -1, "", partServer)) return false;
                        if (!CheckValidChars(Properties.Resources.locNameInstance,
                                    1, -1, "", partInstance)) return false;
                        Server = partServer;
                        Instance = partInstance;
                    }
                    else
                    {
                        // just a plain old server name
                        if (_DEBUGMODE) Console.WriteLine("Server:*" + Server);
                        if (!CheckValidChars(Properties.Resources.locNameServer,
                                    1, -1, "", Server)) return false;
                    }
                }
                return true;
            }
        }

        // --------------------
        // -U validate
        private static bool ValidateOption_U
        {
            get
            {
                if (UserName.Length > 0)
                {
                    if (UserName.Contains("\\"))
                    {
                        string[] ParsedUserName = UserName.Split(new Char[] {'\\'}, 2);
                        Domain = ParsedUserName[0];
                        UserName = ParsedUserName[1];
                    }
                    else if (!httpConnection)
                    {
                        // For a TCP/IP connection, the username must be in the format <domain>\<username>
                        Console.WriteLine(Msg(Properties.Resources.locOptionUFormatErr));
                        return false;
                    }
                    if (!CheckValidChars(Properties.Resources.locNameUsername,
                                1, -1, "", UserName)) return false;
                    if (!CheckValidChars(Properties.Resources.locNameDomain,
                                1, -1, "", Domain)) return false;
                }
                return true;
            }
        }

        // --------------------
        // -P validate
        private static bool ValidateOption_P
        {
            get
            {
                if (Password.Length > 0)
                {
                    if (!CheckValidChars(Properties.Resources.locNamePassword,
                            0, -1, "", Password)) return false;
                }
                return true;
            }
        }
            
        // --------------------
        // -d validate
        private static bool ValidateOption_d
        {
            get
            {
                if (Database.Length > 0)
                {
                    if (!CheckValidChars(Properties.Resources.locNameDatabase,
                            1, 128, "", Database)) return false;
                }
                return true;
            }
        }


        // --------------------
        // -Tf validate
        private static bool ValidateOption_Tf
        {
            get
            {
                // only 2 supported options -- validate their names here
                TraceFormat = TraceFormat.ToLower(CultureInfo.CurrentCulture);
                if ((TraceFormat != "text") && (TraceFormat != "csv"))
                {
                    Console.WriteLine(Msg(Properties.Resources.locTraceInvalidFormatErr), TraceFormat);
                    return false;
                }
                return true;
            }
        }

        // --------------------
        // -Tl validate
        private static bool ValidateOption_Tl
        {
            get
            {
                // only 4 supported options -- validate their names here
                TraceLevel = TraceLevel.ToLower(CultureInfo.CurrentCulture);
                if ((TraceLevel != "high") && (TraceLevel != "medium") &&
                    (TraceLevel != "low") && (TraceLevel != "duration") && 
                    (TraceLevel != "duration-result"))
                {
                    Console.WriteLine(Msg(Properties.Resources.locTraceInvalidLevelErr), TraceLevel);
                    return false;
                }
                return true;
           }
        }

        // --------------------
        // -Td validate
        private static bool ValidateOption_Td
        {
            get
            {
                if (TraceDelim.Length > 1)
                {
                    Console.WriteLine(Msg(Properties.Resources.locTraceInvalidDelimErr), TraceDelim);
                    return false;
                }
                return true;
           }
        }

       // --------------------
       // validate a timeout value
        private static bool ValidateTimeout(string Option, string Timeout)
        {
            if (Timeout.Length > 0)
                {
                    int t;
                    if ((t = GetTimeout(Option, Timeout)) == -1) return false;
                    if (!CheckTimeout(Option, t, maxTimeout)) return false;
                }
            return true;
        }

        // ------------------------------------------------------------
        // Get the available scripting variables from the OS
        private static void GetScriptingVariables()
        {
            if (Environment.GetEnvironmentVariable("ASCMDUSER") != null)
                UserName = Environment.GetEnvironmentVariable("ASCMDUSER");
            if (Environment.GetEnvironmentVariable("ASCMDPASSWORD") != null)
                Password = Environment.GetEnvironmentVariable("ASCMDPASSWORD");
            if (Environment.GetEnvironmentVariable("ASCMDSERVER") != null)
                Server = Environment.GetEnvironmentVariable("ASCMDSERVER");
            if (Environment.GetEnvironmentVariable("ASCMDDBNAME") != null)
                Database = Environment.GetEnvironmentVariable("ASCMDDBNAME");
            if (Environment.GetEnvironmentVariable("ASCMDQUERYTIMEOUT") != null)
                Timeout = Environment.GetEnvironmentVariable("ASCMDQUERYTIMEOUT");
            if (Environment.GetEnvironmentVariable("ASCMDCONNECTTIMEOUT") != null)
                ConnectTimeout = Environment.GetEnvironmentVariable("ASCMDCONNECTTIMEOUT");
            if (Environment.GetEnvironmentVariable("ASCMDTRACEFORMAT") != null)
                TraceFormat = Environment.GetEnvironmentVariable("ASCMDTRACEFORMAT");
            if (Environment.GetEnvironmentVariable("ASCMDTRACEDELIM") != null)
                TraceDelim = Environment.GetEnvironmentVariable("ASCMDTRACEDELIM");
            if (Environment.GetEnvironmentVariable("ASCMDTRACETIMEOUT") != null)
                TraceTimeout = Environment.GetEnvironmentVariable("ASCMDTRACETIMEOUT");
            if (Environment.GetEnvironmentVariable("ASCMDTRACELEVEL") != null)
                TraceLevel = Environment.GetEnvironmentVariable("ASCMDTRACELEVEL");
            if (Environment.GetEnvironmentVariable("ASCMDEXTENDEDCONNECTSTRING") != null)
                ExtendedConnectstring = Environment.GetEnvironmentVariable("ASCMDEXTENDEDCONNECTSTRING");
        }

        private static void SetScriptingVariables()
        {
            // The -v entries are added to the substituteWith table as they are parsed
            // Here we add command-line entries to the substitution table
            //  * = read-only, cannot be set by outside environments
            // ** = set by ValidateSettings
            substituteWith.Add("ASCMDUSER", UserName);
            substituteWith.Add("ASCMDDOMAIN", Domain);                               // **
            substituteWith.Add("ASCMDPASSWORD", Password);
            substituteWith.Add("ASCMDSERVER", Server);
            substituteWith.Add("ASCMDINSTANCE", Instance);                           // **
            substituteWith.Add("ASCMDHTTPCONNECTION", httpConnection.ToString());    // **
            substituteWith.Add("ASCMDDBNAME", Database);
            substituteWith.Add("ASCMDINPUTFILE", InputFile);                         // *
            substituteWith.Add("ASCMDOUTPUTFILE", OutputFile);                       // *
            substituteWith.Add("ASCMDQUERYTIMEOUT", Timeout);
            substituteWith.Add("ASCMDCONNECTTIMEOUT", ConnectTimeout);
            substituteWith.Add("ASCMDTRACEFILE", TraceFile);                         // *
            substituteWith.Add("ASCMDTRACEFORMAT", TraceFormat);
            substituteWith.Add("ASCMDTRACEDELIM", TraceDelim);
            substituteWith.Add("ASCMDTRACETIMEOUT", TraceTimeout);
            substituteWith.Add("ASCMDTRACELEVEL", TraceLevel);
            substituteWith.Add("ASCMDEXTENDEDCONNECTSTRING", ExtendedConnectstring);
        }

        private static bool CheckValidChars(string TypeOfCheck, int MinLen, int MaxLen, string InvalidChars, string TestString)
        {
            if (MinLen >= 0) // negative MinLen means don't check it
                if (TestString.Length < MinLen)
                {
                    Console.WriteLine(Msg(Properties.Resources.locMinErr), TypeOfCheck, TestString, TestString.Length.ToString(CultureInfo.CurrentCulture), MinLen.ToString(CultureInfo.CurrentCulture));
                    return false;
                }
            if (MaxLen >= 0) // negative MaxLen means don't check it
                if (TestString.Length > MaxLen)
                {
                    Console.WriteLine(Msg(Properties.Resources.locMaxErr), TypeOfCheck, TestString, TestString.Length.ToString(CultureInfo.CurrentCulture), MaxLen.ToString(CultureInfo.CurrentCulture));
                    return false;
                }
            if (TestString.Length > 0) // if we don't have any invalid characters provided, then don't check
            {
                int pos = TestString.IndexOfAny(InvalidChars.ToCharArray()) + 1;
                if (pos > 0)
                {
                    Console.WriteLine(Msg(Properties.Resources.locInvalidCharErr), TypeOfCheck, TestString, pos.ToString(CultureInfo.CurrentCulture));
                    return false;
                }
            }
            return true;
        }

        private static int GetTimeout(string TypeOfCheck, string Timeout)
        {
            int ReturnTimeout;
            if (Int32.TryParse(Timeout.ToString((CultureInfo.CurrentCulture)), 
                out ReturnTimeout))
            {
                return ReturnTimeout;
            }
            else
            {
                Console.WriteLine(Msg(Properties.Resources.locIntegerErr), TypeOfCheck, Timeout);
                return -1;
            }
        }
        
        private static bool CheckTimeout(string TypeOfCheck, int Timeout, int MaxTimeout)
        {
            if (MaxTimeout > 0) // -1 maxTimeout won't run check
            {
                if ((Timeout <= 0) || (Timeout > MaxTimeout))
                {
                    Console.WriteLine(Msg(Properties.Resources.locRangeErr), TypeOfCheck, Timeout, MaxTimeout);
                    return false;
                }
            }
            return true;
        }

        private static bool OptionSeen(string opt)
        {
            Console.WriteLine(Msg(Properties.Resources.locRepeatedOption), opt);
            return false;
        }

        private static bool MissingArg(string opt)
        {
            Console.WriteLine(Msg(Properties.Resources.locParseMissingArgErr), opt);
            return false;
        }

        // --------------------------------------------------------------------
        // Supporting routine -- to show the help message
        private static void ShowHelp()
        {
            string msg = "\n" +
            "usage: ascmd.exe" +
            "\n  [-S server[:port][\\instance]] " +
            "\n       || http[s]://server:port/<vdir>/msmdpump.dll " +
            "\n  [-U domain\\username]   [-P password] " +
            "\n  [-d database]          [-xc extended-connect-string] " +
            "\n  [-i input-file]        [-o output-file] " +
            "\n  [-t query-timeout]     [-tc connect-timeout] " +
            "\n  [-T trace-file]        [-Tt trace-timeout] " +
            "\n  [-Tf text|csv]         [-Td delim-char] " +
            "\n  [-Tl high|medium|low|duration|duration-result] " +
            "\n  [-v var=value...] " +
            "\n  [-Q \"cmdline XMLA script, MDX query or DMX statement\" " +
            "\n  [-? show syntax summary] " +
            "\n\n Note: either -i or -Q must be specified, but not both.";
            // OK write it out
            Console.WriteLine(msg);
        }
    }
}
